//
//  HLUniversalExtention.swift
//  HLDesktop
//
//  Created by 子凡 on 2025/4/10.
//

import Foundation
import UIKit
import CryptoKit //md5
import Photos
//import SDWebImage
import SDWebImageWebPCoder
//375*667(iphone8) 414*896(ihponex)
let screen_W = UIScreen.main.bounds.width
let screen_H = UIScreen.main.bounds.height
//以375*667为基数的宽高比例,
let _SCALE_HEIGHT_UNIT =  screen_H/667
let _SCALE_WIDTH_UNIT  =   screen_W/375


extension UIColor{
    //使用rgb方式生成自定义颜色(r: 0~255)
    convenience init(_ r : CGFloat, _ g : CGFloat, _ b : CGFloat) {
        let red = r / 255.0
        let green = g / 255.0
        let blue = b / 255.0
        self.init(red: red, green: green, blue: blue, alpha: 1)
    }
    
    //使用rgba方式生成自定义颜色
    convenience init(_ r : CGFloat, _ g : CGFloat, _ b : CGFloat, _ a : CGFloat) {
        let red = r / 255.0
        let green = g / 255.0
        let blue = b / 255.0
        self.init(red: red, green: green, blue: blue, alpha: a)
    }
    
    convenience init(_ hexString: String) {
        self.init(hexString: hexString, alpha: 1.0)
    }

    convenience init(hexString: String, alpha: Float = 1.0) {
        var red: CGFloat = 0
        var green: CGFloat = 0
        var blue: CGFloat = 0
        var mAlpha: CGFloat = CGFloat(alpha)
        var minusLength = 0

        let scanner = Scanner(string: hexString)

        if hexString.hasPrefix("#") {
            if #available(iOS 13.0, *){
                scanner.currentIndex = scanner.string.index(hexString.startIndex, offsetBy: 1)
            }else{
                scanner.scanLocation = 1
            }
            minusLength = 1
        }
        if hexString.hasPrefix("0x") {
            if #available(iOS 13.0, *){
                scanner.currentIndex = hexString.index(hexString.startIndex, offsetBy: 2)
            }else{
                scanner.scanLocation = 2
            }
            minusLength = 2
        }
        var hexValue: UInt64 = 0
        scanner.scanHexInt64(&hexValue)
        switch hexString.count - minusLength {
        case 3:
            red = CGFloat((hexValue & 0xF00) >> 8) / 15.0
            green = CGFloat((hexValue & 0x0F0) >> 4) / 15.0
            blue = CGFloat(hexValue & 0x00F) / 15.0
        case 4:
            red = CGFloat((hexValue & 0xF000) >> 12) / 15.0
            green = CGFloat((hexValue & 0x0F00) >> 8) / 15.0
            blue = CGFloat((hexValue & 0x00F0) >> 4) / 15.0
            mAlpha = CGFloat(hexValue & 0x000F) / 15.0
        case 6:
            red = CGFloat((hexValue & 0xFF0000) >> 16) / 255.0
            green = CGFloat((hexValue & 0x00FF00) >> 8) / 255.0
            blue = CGFloat(hexValue & 0x0000FF) / 255.0
        case 8:
            red = CGFloat((hexValue & 0xFF000000) >> 24) / 255.0
            green = CGFloat((hexValue & 0x00FF0000) >> 16) / 255.0
            blue = CGFloat((hexValue & 0x0000FF00) >> 8) / 255.0
            mAlpha = CGFloat(hexValue & 0x000000FF) / 255.0
        default:
            break
        }
        self.init(red: red, green: green, blue: blue, alpha: mAlpha)
    }
}

extension UIColor{
    // UIColor -> Hex String获取颜色值的16进制字符串
        var hexString: String? {
            var red: CGFloat = 0
            var green: CGFloat = 0
            var blue: CGFloat = 0
            var alpha: CGFloat = 0
             
            let multiplier = CGFloat(255.999999)
             
            guard self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) else {
                return nil
            }
             
            if alpha == 1.0 {
                return String(
                    format: "#%02lX%02lX%02lX",
                    Int(red * multiplier),
                    Int(green * multiplier),
                    Int(blue * multiplier)
                )
            }
            else {
                return String(
                    format: "#%02lX%02lX%02lX%02lX",
                    Int(red * multiplier),
                    Int(green * multiplier),
                    Int(blue * multiplier),
                    Int(alpha * multiplier)
                )
            }
        }
}

extension String {
    
    public var color: UIColor {
        return UIColor(hexString: self)
    }
    
    public func base64DecodeUrlSafe() -> String? {
        if let _ = self.range(of: ":")?.lowerBound {
            return self //如果已有:，表示该str已为明文，无需再decode
        }
        let base64String = self.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
        let padding = base64String.count + (base64String.count % 4 != 0 ? (4 - base64String.count % 4) : 0)
        if let decodedData = Data(base64Encoded: base64String.padding(toLength: padding, withPad: "=", startingAt: 0), options: NSData.Base64DecodingOptions(rawValue: 0)), let decodedString = NSString(data: decodedData, encoding: String.Encoding.utf8.rawValue) {
            return decodedString as String
        }
        return nil
    }
    
    public func base64DecodeStr() -> String?{
        let base64String = self.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
        let padding = base64String.count + (base64String.count % 4 != 0 ? (4 - base64String.count % 4) : 0)
        if let decodedData = Data(base64Encoded: base64String.padding(toLength: padding, withPad: "=", startingAt: 0), options: NSData.Base64DecodingOptions(rawValue: 0)), let decodedString = NSString(data: decodedData, encoding: String.Encoding.utf8.rawValue) {
            return decodedString as String
        }
        return nil
    }
    
    public func trimSpace()->String{
        return self.trimmingCharacters(in: .whitespaces)
    }
}

extension UIColor{
//兼容13以下系统的默认背景色
    public class var defaultBackgroundColor:  UIColor {
        get{
            if #available(iOS 13.0, *){
                return .systemBackground
            }else{
                return .white
            }
        }
    }
    
//兼容13以下系统的灰色背景
    public class var defaultGrayBgColor:  UIColor {
        get{
            if #available(iOS 13.0, *){
                return .secondarySystemBackground
            }else{
                return .systemGray
            }
        }
    }
    
//兼容13以下系统的默认字体颜色
    public class var defaultTextColor:  UIColor {
        get{
            if #available(iOS 13.0, *){
                return .label
            }else{
                return .black
            }
        }
    }
}


extension UIDevice {
    static func autoSacle_Width(_ width:CGFloat)->CGFloat{
        return width * _SCALE_WIDTH_UNIT
    }
    static func autoSacle_Height(_ height:CGFloat)->CGFloat{
        return height * _SCALE_HEIGHT_UNIT
    }
    
    /// 顶部安全区高度
    static func ui_safeDistanceTop() -> CGFloat {
        if #available(iOS 13.0, *) {
            let scene = UIApplication.shared.connectedScenes.first
            guard let windowScene = scene as? UIWindowScene else { return 0 }
            guard let window = windowScene.windows.first else {
                guard let statusBarManager = windowScene.statusBarManager else { return 0 }
                return statusBarManager.statusBarFrame.height
//在viewLoad或willappear时调用,即便prefersStatusBarHidden=true，xR亦为48，
//在didAppear之后调用，如果prefersStatusBarHidden=false为48，=true时不管刘海屏或灵动岛屏都为0
//有刘海的高度可能为44、47、48,灵动岛54
            }
            return window.safeAreaInsets.top
            //xr上safeAreaInsets(t=48,l=0,b=34,r=0)(prefersStatusBarHidden=true隐藏状态栏情况下亦是),
            //但前提是在didFinishLaunchingWithOptions里创建window
            //如果是在SenceDelegate里创建window,xr上safeAreaInsets(t=0,l=0,b=0,r=0)
        } else{
            guard let window = UIApplication.shared.windows.first else { return 0 }
            let _top = window.safeAreaInsets.top
            //iphone6.iOS12下：safeAreaInsets(t=20,l=0,b=0,r=0),
            return _top
        }
    }
    
    /// 底部安全区高度
    static func ui_safeDistanceBottom() -> CGFloat {
        if #available(iOS 13.0, *) {
            let scene = UIApplication.shared.connectedScenes.first
            guard let windowScene = scene as? UIWindowScene else { return 0 }
            guard let window = windowScene.windows.first else { return 0 }
            return window.safeAreaInsets.bottom
        } else {//if #available(iOS 11.0, *)
            guard let window = UIApplication.shared.windows.first else { return 0 }
            return window.safeAreaInsets.bottom
        }
//        return 0;
    }
    
    /// 顶部状态栏高度（包括安全区）
    static func ui_statusBarHeight() -> CGFloat {
//有刘海的高度可能为44、47、48,无刘海为20；有无刘海在prefersStatusBarHidden=true，且在didAppear之后调用都为0
        var statusBarHeight: CGFloat = 0
        if #available(iOS 13.0, *) {
            let scene = UIApplication.shared.connectedScenes.first
            guard let windowScene = scene as? UIWindowScene else { return 0 }
            guard let statusBarManager = windowScene.statusBarManager else { return 0 }
            statusBarHeight = statusBarManager.statusBarFrame.height
//在viewLoad或willappear时调用,即便prefersStatusBarHidden=true，xR亦为48，appear之后调用，如果prefersStatusBarHidden=false为48，=true则为0
        } else {
            statusBarHeight = UIApplication.shared.statusBarFrame.height
            //iphone6.iOS12下=20，但prefersStatusBarHidden=true隐藏状态栏情况下=0
        }
        return statusBarHeight
    }
    
//!!!!所以发现通过系统参数获取，情况太多，还不如能过机型来判断更可靠？
    
    /// 导航栏高度
    static func ui_navigationBarHeight() -> CGFloat {
        return 44.0
    }
    
    /// 状态栏+导航栏的高度
    static func ui_navigationFullHeight() -> CGFloat {
        return UIDevice.ui_statusBarHeight() + UIDevice.ui_navigationBarHeight()
    }
    
    /// 底部导航栏高度
    static func ui_tabBarHeight() -> CGFloat {
        return 49.0
    }
    
    /// 底部导航栏高度（包括安全区）
    static func ui_tabBarFullHeight() -> CGFloat {
        return UIDevice.ui_tabBarHeight() + UIDevice.ui_safeDistanceBottom()
    }
}

/*
 button点击事件封装
 */
extension UIButton {
      // 定义关联的Key
      private struct UIButtonKeys {
         static var clickKey = "UIButton+Extension+ActionKey"
      }
      
      func addActionWithBlock(_ closure: @escaping (_ sender:UIButton)->()) {
//把闭包作为一个值 先保存起来;
//@escaping定义逃逸类型的闭包，
//如果一个闭包被作为一个参数传递给一个函数，并且在函数return之后才被唤起执行，那么这个闭包是逃逸闭包。
/*
 关联是指把两个对象相互关联起来，使得其中的一个对象作为另外一个对象的一部分。
 其本质是在类的定义之外为类增加额外的存储空间。
 
 使用关联，我们可以不用修改类的定义而为其对象增加存储空间。
 这在我们无法访问到类的源码的时候，或者是考虑到二进制兼容性的时候，非常有用。
 关联是基于关键字的，因此，我们可以为任何对象增加任意多的关联，每个都使用不同的关键字即可。
 关联是可以保证被关联的对象在关联对象的整个生命周期都是可用的（在垃圾自动回收环境下也不会导致资源不可回收）。
 
 objc_setAssociatedObject为OC的运行时函数
 void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
 id object                     :表示关联者，是一个对象，变量名理所当然也是object
 const void *key               :获取被关联者的索引key
 id value                      :被关联者，这里是一个block
 objc_AssociationPolicy policy :关联时采用的协议，有assign，retain，copy等协议，一般使用OBJC_ASSOCIATION_RETAIN_NONATOMIC
 关键字 : 是一个void类型的指针。每一个关联的关键字必须是唯一的。通常都是会采用静态变量来作为关键字。
 关联策略表明了相关的对象是通过赋值(assign)，保留引用(retain)还是复制(copy)的方式进行关联的；
 还有这种关联是原子的还是非原子的。这里的关联策略和声明属性时的很类似。这种关联策略是通过使用预先定义好的常量来表示的。
*/
         objc_setAssociatedObject(self, &UIButtonKeys.clickKey, closure, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY)
/*
1、  OBJC_ASSOCIATION_ASSIGN              相当于weak
2 、 OBJC_ASSOCIATION_RETAIN_NONATOMIC    相当于strong和nonatomic
3、  OBJC_ASSOCIATION_COPY_NONATOMIC      相当于copy和nonatomic
4、  OBJC_ASSOCIATION_RETAIN              相当于strong和atomic
5、  OBJC_ASSOCIATION_COPY                相当于copy和atomic
 */
        
//给按钮添加传统的点击事件，调用写好的方法
         self.addTarget(self, action: #selector(my_ActionForTapGesture), for: .touchUpInside)
      }
    
      @objc private func my_ActionForTapGesture() {
         //获取闭包值
         let obj = objc_getAssociatedObject(self, &UIButtonKeys.clickKey)
         if let action = obj as? (_ sender:UIButton)->() {
             //调用闭包
             action(self)
         }
      }
    
    func setVerticalImage(_ spacing:CGFloat){
//图片在上，文字在下显示
        let imageSize = self.imageView?.frame.size
        let titleSize = self.titleLabel?.frame.size
        let totalHeight = imageSize!.height + titleSize!.height + spacing
        self.imageEdgeInsets = UIEdgeInsets(top: -(totalHeight - imageSize!.height), left: 0, bottom: 0, right: -titleSize!.width)
        self.titleEdgeInsets = UIEdgeInsets(top: 0, left: -imageSize!.width, bottom: -(totalHeight-titleSize!.height), right: 0)
    }
}

extension UIView{
//任意角的圆角
     func easyRoundRect(corner: UIRectCorner, radii: CGSize){
           let maskPath = UIBezierPath.init(roundedRect: bounds, byRoundingCorners: corner, cornerRadii: radii)
           let maskLayer = CAShapeLayer.init()
           maskLayer.frame = bounds
           maskLayer.path = maskPath.cgPath
          self.layer.mask = maskLayer
    }
    
    func setShadow(shadowColor:UIColor,offset:CGSize,opacity:Float,radius:CGFloat){
        self.layer.shadowColor = shadowColor.cgColor
        self.layer.shadowOpacity = opacity //透明度
        self.layer.cornerRadius = radius
        self.layer.shadowOffset = offset
    }
    
    //给View绘制其自身Frame大小的虚线边框，eg: lineView.addDashdeBorderLayer(color: lineColor, lineWidth: 0.5)
    func addDashdeBorderLayer(color:UIColor,lineWidth lWidth:CGFloat,lineDashPattern: [NSNumber]?=[4,3]){
        let shapeLayer = CAShapeLayer()
        shapeLayer.bounds = self.bounds
        shapeLayer.anchorPoint = CGPoint(x: 0, y: 0)
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = color.cgColor
        shapeLayer.lineWidth = lWidth //边框线条粗细
        shapeLayer.lineJoin = CAShapeLayerLineJoin.round
        shapeLayer.lineDashPattern = lineDashPattern
        let path = CGMutablePath()
        path.move(to: CGPoint(x: 0, y: 0))
        if lWidth == self.bounds.width{
//绘制单条竖线
            path.addLine(to: CGPoint(x: 0, y: self.bounds.height))
        }else if lWidth == self.bounds.height{
//绘制单条横线
            path.addLine(to: CGPoint(x: self.bounds.width, y: 0))
        }else{
//绘制整个view的边框
            path.addLine(to: CGPoint(x: self.bounds.width, y: 0))//上水平
            path.addLine(to: CGPoint(x: self.bounds.width, y: self.bounds.height))//右垂直
            path.addLine(to: CGPoint(x: 0, y: self.bounds.height))//下水平
            path.addLine(to: CGPoint(x: 0, y: 0))//左垂直
        }
        shapeLayer.path = path
//        self.layer.addSublayer(shapeLayer)
        self.layer.insertSublayer(shapeLayer, at: 0)
    }
    
////在本View上显示一个Toast,默认2s
    func showToast(_ msg:String,atPosition isBottom:Bool=true, withSpace iSpace:CGFloat=30, delayTime showTime: Double = 2.0){
        let lastToView = self.viewWithTag(1758)
        if lastToView != nil{
            NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(removeToast(_:)), object: "1")
            lastToView?.removeFromSuperview()
        }else{
            let lastView = self.viewWithTag(1759)
            if lastView != nil{
                NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(removeToast(_:)), object: "0")
                lastView?.removeFromSuperview()
            }
        }

        let toastWidth = self.bounds.width - iSpace*2
        let toastHeight = 50.0
        let toastView = UIView()
        if isBottom{
            //toast显示在底部
            toastView.frame = CGRect(x: iSpace, y: self.bounds.height + 10, width: toastWidth, height: toastHeight)
        }else{
            //toast显示在顶部
            toastView.frame = CGRect(x: iSpace, y: -100, width: toastWidth, height: toastHeight)
        }
        
        self.addSubview(toastView)
        toastView.tag = isBottom ? 1758 : 1759
        toastView.backgroundColor = isBottom ? .white.withAlphaComponent(0.85) : .black.withAlphaComponent(0.75)
        toastView.setShadow(shadowColor: prjTitleColor, offset: CGSize(width: 3, height: 3), opacity: 0.75, radius: toastHeight/2)
        
        let label = UILabel(frame: CGRect(x: 0, y: 0, width: toastWidth , height: toastHeight))
        toastView.addSubview(label)
        label.numberOfLines = 0
        label.adjustsFontSizeToFitWidth = true
        label.textColor = prjTitleColor
        label.font = .systemFont(ofSize: 16)
        label.text = msg
        label.textAlignment = .center
        
//usingSpringWithDamping传入数值为0.0~1.0,数值越大,弹性越不明显
//initialSpringVelocity数值越大，初始速度越大
        UIView.animate(withDuration: 0.5, delay:0.25, usingSpringWithDamping: 0.4, initialSpringVelocity: 25, options: [.curveEaseInOut]) {
            if isBottom{
                toastView.frame = CGRect(x: iSpace, y: self.bounds.height - 150, width: toastWidth, height: toastHeight)
            }else{
                toastView.frame = CGRect(x: iSpace, y: 100, width: toastWidth, height: toastHeight)
            }
        }
        
        self.perform(#selector(removeToast(_:)), with: isBottom ? "1" : "0", afterDelay: showTime)
    }
    
    @objc func removeToast(_ isBottom:String){
        if let toastView = self.viewWithTag(("1" ==  isBottom) ? 1758 : 1759) {
            let frame = toastView.frame
            UIView.animate(withDuration: 0.25, delay: 0,options: [.curveLinear]) {
                if "1" == isBottom{//底部位置
                    toastView.frame = CGRect(x: frame.origin.x, y: self.bounds.height + 10 , width: frame.width, height: frame.height)
                }else{
                    toastView.frame = CGRect(x: frame.origin.x, y: -100 , width: frame.width, height: frame.height)
                }
            } completion: { okflag in
                toastView.removeFromSuperview()
            }
        }
    }
    
//截取View屏幕并转为View,类似copyView
    func snapshot2View()->UIView?{
//注意，如View在后台时，会返回nil
        return self.snapshotView(afterScreenUpdates: false)
    }
    
//截取整个view的图像并转为image
    func snapshot2Image(_ scale:CGFloat? = nil) -> UIImage? {
        let okScale = (scale != nil) ? scale! : UIScreen.main.scale //UIScreen.main.scale 1=1x,2=2x,3=3x
        UIGraphicsBeginImageContextWithOptions(self.frame.size,false, okScale)
        self.layer.render(in:UIGraphicsGetCurrentContext()!) //渲染到上下文
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }
    
//截取view的某个区域(shotRect)图像并转为image
    func snapshot2Image(with shotRect:CGRect,afterScreenUpdates flag:Bool=true) -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(shotRect.size,false, UIScreen.main.scale)
        let _ = UIGraphicsGetImageFromCurrentImageContext()
        self.drawHierarchy(in: CGRect(origin: CGPoint(x: -shotRect.origin.x, y: -shotRect.origin.y), size: self.bounds.size), afterScreenUpdates: flag)
//注意drawHierarchy的Rect设置
//afterScreenUpdates如果是true, 则是addSubview之后,  如果是false,则是addSubview之前
//比较费时，如果截取源图较大，截取块数较多，有可能出现缓存太大而崩溃
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }
    
//将view分裂成指定大小的碎片，然后下跌UIView.animate位移动画
    func animateToCrush(crushSize:CGSize,xSmall:CGFloat = -10.0,xBig:CGFloat=10.0, duration:TimeInterval=0.5,delayTime:TimeInterval=0.25) {
        let tmpView = UIView(frame: self.frame) //创建一个相同位置的在前面放置撕碎的碎片
        self.superview?.insertSubview(tmpView, aboveSubview: self)
        tmpView.clipsToBounds = true
        
        var snapshots:[UIImageView] = []
        for x in stride(from: CGFloat(0), through: self.bounds.width, by: crushSize.width) {
            for y in stride(from: CGFloat(0), to: self.bounds.height, by: crushSize.height) {
                let snapshotRegion = CGRect(origin: CGPoint(x: x, y: y), size: crushSize)
                if let strideImage = self.snapshot2Image(with: snapshotRegion){
                    //撕成小碎片并绘制到临时tmpImgView上
                    let tmpImgView = UIImageView(frame: snapshotRegion)
                    tmpView.addSubview(tmpImgView)
                    tmpImgView.image = strideImage
//                    tmpImgView.layer.borderWidth = 0.25 //test.标识是否碎裂成功
//                    tmpImgView.layer.borderColor = UIColor.yellow.cgColor
                    snapshots.append(tmpImgView)
                }
            }
        }
//先将本身移除
        self.removeFromSuperview()
//然后将碎片下跌并移除
        UIView.animate(
            withDuration:
                0.15,
            delay: 0,
            options: UIView.AnimationOptions.curveLinear,
            animations: {
                for perView in snapshots {
                    let xOffset = HLUniversalFunc.randomFloatBetween(xSmall , and: xBig)
                    let yOffset = HLUniversalFunc.randomFloatBetween(xSmall , and: xBig)
                    perView.frame = perView.frame.offsetBy(dx: CGFloat(xOffset), dy: CGFloat(yOffset)) //先打碎
                }
            },
            completion: { _ in
                UIView.animate(withDuration: duration, delay: delayTime) {
                    for perView in snapshots {
                        let xOffset = HLUniversalFunc.randomFloatBetween(xSmall , and: xBig)
                        let yOffset = HLUniversalFunc.randomFloatBetween(tmpView.frame.height, and: tmpView.frame.height * 1.2)
                        perView.frame = perView.frame.offsetBy(dx: CGFloat(xOffset), dy: CGFloat(yOffset)) //然后下跌
                    }
                } completion: { _ in
                    for perView in snapshots {
                        perView.removeFromSuperview()
                    }
                    tmpView.removeFromSuperview() //移除临时view
                }
                
            }
        )
    }
    

//在view上显示波纹动画
    func addRippleEffect(rippleSize: CGSize,position:CGPoint,color:UIColor,duration:CFTimeInterval = 0.3,repeatCount:Float? = 1,lineWidth:CGFloat? = 0,instanceCount:Int? = 0) {
        /*! Creates a circular path around the view*/
        let path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: rippleSize.width, height: rippleSize.height))
        /*! Position where the shape layer should be */
//        let shapePosition =  CGPoint(x: referenceView.bounds.size.width / 2.0, y: referenceView.bounds.size.height / 2.0)
        let rippleShape = CAShapeLayer()
        rippleShape.bounds = CGRect(x: 0, y: 0, width: rippleSize.width, height: rippleSize.height)
        rippleShape.path = path.cgPath
        if 0 == lineWidth{ //扩散模式
            rippleShape.fillColor = color.cgColor
        }else{//雷达模式
            rippleShape.strokeColor = color.cgColor
            rippleShape.lineWidth = lineWidth!
        }
        rippleShape.position = position // shapePosition
        rippleShape.opacity = 0
        
        /*! Add the ripple layer as the sublayer of the reference view */
//        referenceView.layer.addSublayer(rippleShape)
        
        /*! Create scale animation of the ripples */
        let scaleAnim = CABasicAnimation(keyPath: "transform.scale")
        scaleAnim.fromValue = NSValue(caTransform3D: CATransform3DIdentity)
        scaleAnim.toValue = NSValue(caTransform3D: CATransform3DMakeScale(2, 2, 1))
        /*! Create animation for opacity of the ripples */
        let opacityAnim = CABasicAnimation(keyPath: "opacity")
        opacityAnim.fromValue = 1
        opacityAnim.toValue = nil
        /*! Group the opacity and scale animations */
        let animation = CAAnimationGroup()
        animation.animations = [scaleAnim, opacityAnim]
        animation.timingFunction = CAMediaTimingFunction(name: .easeOut)
        animation.duration = duration //CFTimeInterval(0.7)
        animation.repeatCount = repeatCount!
        animation.isRemovedOnCompletion = true
        rippleShape.add(animation, forKey: "rippleEffect")
        
        if 0 == instanceCount{
            self.layer.addSublayer(rippleShape)
            DispatchQueue.main.asyncAfter(deadline: .now()+animation.duration+0.1) {
                rippleShape.removeFromSuperlayer()
            }
        }else{
            //如果要多重动态波，则
            let rep = CAReplicatorLayer()
            rep.frame = rippleShape.bounds
            rep.instanceCount = instanceCount!
            rep.instanceDelay = duration/2
            rep.addSublayer(rippleShape)
            self.layer.addSublayer(rep)
            DispatchQueue.main.asyncAfter(deadline: .now()+animation.duration+0.1) {
                rippleShape.removeFromSuperlayer()
                rep.removeFromSuperlayer()
            }
        }
        
    }
    
    func animateCircular(withDuration duration: TimeInterval, center: CGPoint, revert: Bool = false, animations: () -> Void, completion: ((Bool) -> Void)? = nil) {
//镜头开关动画缩放，revert=true,表镜头关闭(缩小)，=false表示镜头开启(放大)
        if let snapshot = snapshotView(afterScreenUpdates: false){
            snapshot.frame = bounds
            self.addSubview(snapshot)
            
            let center = convert(center, to: snapshot)
            let radius: CGFloat = {
                let x = max(center.x, frame.width - center.x)
                let y = max(center.y, frame.height - center.y)
                return sqrt(x * x + y * y)
            }()
            var animation : CircularRevealAnimator
            if !revert {
                animation = CircularRevealAnimator(layer: snapshot.layer, center: center, startRadius: 0, endRadius: radius, invert: true)
            } else {
                animation = CircularRevealAnimator(layer: snapshot.layer, center: center, startRadius: radius, endRadius: 0, invert: false)
            }
            animation.duration = duration
            animation.completion = {
                snapshot.removeFromSuperview()
                completion?(true)
            }
            animation.start()
            animations()
        }
    }
    
}

extension String {
    
    var totoImage: UIImage? {
        return UIImage(named: self)
    }
    
    var totoTemplateImage: UIImage? {
        return UIImage(named: self)?.withRenderingMode(.alwaysTemplate)
    }
    
    var totoOriginalImage: UIImage? {
        return UIImage(named: self)?.withRenderingMode(.alwaysOriginal)
    }

}

extension UIScrollView{
    func removeTopBlankHeadArea(){
//消除顶部空白行
        if #available(iOS 13.0, *) {
            self.contentInsetAdjustmentBehavior = .never
        }
    }
    
}

//MARK: - ImageView加载本地及网络图片
extension UIImageView{
//设置图片:支持网络图片、本地图片、及系统图片,返回true表示为系统图标
    func setOKImgData(imgurl:String)->Bool{
        if imgurl.isEmpty {
            return false
        }
        var md5key = "aiScreen.pCache."
        if imgurl.hasPrefix("aiScreen.pCache.album.") {
            md5key = imgurl
        }else{
            md5key = md5key.appending(imgurl.hl_md5())
        }
        if let data = UserDefaults.standard.data(forKey: md5key){
            self.startGifWithImageData(imgdata: data as NSData) //兼容jpg、gif的data
            return true
        }
        return false
    }
    
    func setImageWithStr(imgurl:String,defName:String?=nil){
//先判断是否已缓存
        if imgurl.isEmpty{
            if defName != nil{
                self.setImageWithStr(imgurl: defName!)
            }
            return
        }
        
        if setOKImgData(imgurl: imgurl){
            return
        }
        if imgurl.hasPrefix("aiScreen.pCache.album."){
            if defName != nil{
                self.setImageWithStr(imgurl: defName!)
            }
            return
        }
        
        let lowUrl = imgurl.lowercased()
        if lowUrl.hasPrefix("http"){
            loadImageFrom(imageurl: imgurl,And: nil) { picUrl in
                if picUrl == nil{
                    if defName != nil{
                        self.setImageWithStr(imgurl: defName!)
                    }
                }
            }
        }else if lowUrl.hasSuffix(".gif") || lowUrl.hasPrefix("file://"){
            //本地gif
            if defName != nil{
                self.setImageWithStr(imgurl: defName!)
            }
            self.startGifWithImageName(imgurl)
        }else if lowUrl.hasSuffix(".webp"){ //非http类型的本地webp文件
            guard let path = Bundle.main.path(forResource: imgurl, ofType: "") else {
//path =  /private/var/containers/Bundle/Application/8B0747A7-92AD-4838-835D-3CCE5F1F39EE/HLDesktop.app/xxx.webp
                if defName != nil{
                    self.setImageWithStr(imgurl: defName!)
                }
                return
            }
//SDWebImage
//self.sd_setImage(with: URL(string: "https://www.tutorialexample.com/wp-content/uploads/2020/09/gif-test-2.webp"), placeholderImage:nil)
//可以加载出网络webp，但动画不会动，改用：
//let webpURL: URL
//let imageView: SDAnimatedImageView
//imageView.shouldIncrementalLoad = true
//imageView.sd_setImage(with: webpURL, placeholderImage: nil, options: [.progressiveLoad])
            
//self.sd_setImage(with: URL(string: path), placeholderImage: (defName != nil) ? UIImage(named: defName!) : nil)
//此种方式不能显示webp图片，需用下面的方法：
            let webpData = NSData(contentsOfFile: path)
            if let webpImage = UIImage.sd_image(with: webpData as? Data){//引用的是SDWebImage中的UIImage+MultiFormat
//sd_imageWithData！！！在iOS13上webp显示失败,因为从SDWebImage 5.0版本开始，我们将WebP支持代码和libwebp从Core Repo移到了这个独立的SDWebImageWebPCoder中，
//但在iOS16上却是可以的，这是因为苹果的ImageIO支持iOS 14/tvOS 14/watchOS 7/macOS 11的WebP解码，因此这些平台上的SDWebImage也可以解码WebP图像
//在iOS13上sd_imageWithData会返回为nil,应该使用SDImageWebPCoder的sd_imagewithWebPData
                self.image = webpImage
            }else if let ios13_webP = UIImage.sd_image(withWebPData: webpData as? Data){ //引用的是"UIImage+WebP.h"中的SDImageWebPCoder
                self.image = ios13_webP //实测OK，可以支持iOS13上显示webP
            }else if defName != nil{
                self.setImageWithStr(imgurl: defName!)
            }
            
        }else if lowUrl.hasSuffix(".png") || lowUrl.hasSuffix(".jpg") || lowUrl.hasSuffix(".jpeg"){
//本地资源
            self.image = UIImage(named: imgurl)
        }
        else if !imgurl.isEmpty{
//system图标
            if (lowUrl.contains("#syscolor=")){
                let tmpArr = imgurl.components(separatedBy: "#syscolor=")
                if (2 == tmpArr.count){//带前景色
                    let foreColor = UIColor(tmpArr[1])
                    self.image = UIImage(systemName: tmpArr[0])?.withTintColor(foreColor,renderingMode: .alwaysOriginal)
                }else if (3 == tmpArr.count){//带前景和背景色
                    let foreColor = UIColor(tmpArr[1])
                    let backColor = UIColor(tmpArr[2])
                    self.image = UIImage(systemName: tmpArr[0])?.withTintColor(foreColor,renderingMode: .alwaysOriginal)
                    self.backgroundColor = backColor
                }else{//默认颜色
                    self.image = UIImage(systemName: tmpArr[0])?.withTintColor(.secondaryLabel,renderingMode: .alwaysOriginal)
                }
            }else{//默认颜色
                self.image = UIImage(systemName: imgurl)?.withTintColor(.secondaryLabel,renderingMode: .alwaysOriginal)
            }
        }else{
//默认
            if defName != nil{
                self.setImageWithStr(imgurl: defName!)
            }
        }
    }
    
//    //异步加载网络图片
//    func loadImageFrom(imageurl : String,with defName:String?=nil,_ isLaunch:Bool = false){
////isLaunch=true时表示程序刚启动状态，此时不执行UserDefaults.standard.set保存Data
//        if setOKImgData(imgurl: imageurl){
//            return
//        }
//        if defName != nil{
//            self.setImageWithStr(imgurl: defName!)
//        }
//
//        if imageurl.lowercased().hasSuffix(".webp"){
//            guard let urlPath = URL(string: imageurl) else {
//                print("loadImageFrom: Source for the url error")
//                if defName != nil{
//                    self.setImageWithStr(imgurl: defName!)
//                }
//                return
//            }
//            self.sd_setImage(with: urlPath, placeholderImage: nil) //需在setting的Preprocessor Macros中设置SD_WEBP=1
//            return
//        }
//
//        //创建URL对象
//        let url = URL(string: imageurl)!
//        //创建请求对象
//        let request = URLRequest(url: url)
//        let session = URLSession.shared
//        if defName != nil{
//            self.setImageWithStr(imgurl: defName!) //先加载默认图标占位
//        }
//        let dataTask = session.dataTask(with: request, completionHandler: {(data, response, error) -> Void in
//            if error != nil{
//                if defName != nil{
//                    self.setImageWithStr(imgurl: defName!)
//                }
//                print(error.debugDescription)
//            }else{
//                //将图片数据赋予UIImage
//                if data != nil{
//                    //说明data是OK的,则缓存以备下次取出
//                    if data!.count <= 10240000 && !isLaunch{ //>10M太大则不保存
//                        let md5key = "aiScreen.pCache." + imageurl.hl_md5()
//                        UserDefaults.standard.set(data!, forKey: md5key) //当缓存>1G后，即使保存一个1M大小的数据都会很慢，甚至崩溃,try也没用
//                        UserDefaults.standard.synchronize()
//                    }
//                    DispatchQueue.main.async {
//                        let lowUrl = imageurl.lowercased()
//                        if lowUrl.hasSuffix(".jpg") || lowUrl.hasSuffix(".jpeg") || lowUrl.hasSuffix(".png"){
//                            if let imageData = data{
//                                let img = UIImage(data:imageData)// 这里需要改UI，需要回到主线程
//                                self.image = img
//                            }
//                        }else{
//                            self.startGifWithImageData(imgdata: data as? NSData)
//                        }
//                    }
//
//                }else if defName != nil{
//                    self.setImageWithStr(imgurl: defName!)
//                }
//            }
//        }) as URLSessionTask
//        //使用resume方法启动任务
//        dataTask.resume()
//    }
    
    
    //异步加载网络图片,seccondUrl备用网络地址
    typealias os_block_string = (_ picUrl:String?) -> Void
    func loadImageFrom(imageurl : String,And seccondUrl:String?=nil,CompleteBlock:os_block_string?=nil){
//isLaunch=true时表示程序刚启动状态，此时不执行UserDefaults.standard.set保存Data
        if setOKImgData(imgurl: imageurl){
            CompleteBlock?(imageurl)
            return
        }

        guard let urlPath = URL(string: imageurl) else {
            print("loadImageFrom: Source for the url error")
            CompleteBlock?(nil)//表示加载图片出错
            return
        }
//sd_setImageWithUrl本身已有缓存机制
//Option:SDWebImageContinueInBackground后台下载,如果用了，会返回多次,image=nil,但其实并不是url有问题
        self.sd_setImage(with: urlPath, placeholderImage: nil) { [weak self](image, error, cacheType, url) in
            if error != nil || image == nil{
                if let strBkUrl = seccondUrl{
                    self?.loadImageFrom(imageurl: strBkUrl,And: nil,CompleteBlock: CompleteBlock)
                }else{
                    CompleteBlock?(nil) //表示加载图片出错
                }
            }else{
                CompleteBlock?(urlPath.absoluteString)
            }
        }

//        //创建URL对象
//        let url = URL(string: imageurl)!
//        //创建请求对象
//        let request = URLRequest(url: url)
//        let session = URLSession.shared
//        let dataTask = session.dataTask(with: request, completionHandler: { [self](data, response, error) -> Void in
//            if error != nil || data == nil || data!.count<1024{
//                //简书和有道不存的url图片地址不会返回失败，而返回一个JsonData或xmlData,故其字节数很少，小于1K
//                print(error.debugDescription)
//                if let strBkUrl = seccondUrl{
//                    DispatchQueue.main.async { [weak self] in
//                        self?.perform(#selector(self?.tryLoadSecondUrl(_:)), with: strBkUrl, afterDelay: 0.5)
//                        CompleteBlock?()
//                    }
//                }
//            }else{
//                //将图片数据赋予UIImage
//                if data != nil{
////                    if jSon =
//                    //说明data是OK的,则缓存以备下次取出
//                    if data!.count <= 10240000{ //>10M太大则不保存
//                        let md5key = "aiScreen.pCache." + imageurl.hl_md5()
//                        UserDefaults.standard.set(data!, forKey: md5key) //保存>1M大小的数据都会非常慢，甚至崩溃,try也没用
//                        UserDefaults.standard.synchronize()
//                    }
//                    DispatchQueue.main.async {
//                        let lowUrl = imageurl.lowercased()
//                        if lowUrl.hasSuffix(".jpg") || lowUrl.hasSuffix(".jpeg") || lowUrl.hasSuffix(".png"){
//                            if let imageData = data{
//                                let img = UIImage(data:imageData)// 这里需要改UI，需要回到主线程
//                                self.image = img
//                            }
//                        }else{
//                            self.startGifWithImageData(imgdata: data as? NSData)
//                        }
//                        CompleteBlock?()
//                    }
//                }
//            }
//        }) as URLSessionTask
//        //使用resume方法启动任务
//        dataTask.resume()
    }
    
    
    @objc func tryLoadSecondUrl(_ imageUrl:String?){
        if let imgurl = imageUrl{
            if setOKImgData(imgurl: imgurl){
                return
            }
            guard let urlPath = URL(string: imgurl) else {
                print("loadImageFrom: Source for the url error")
                return
            }
           
            self.sd_setImage(with: urlPath, placeholderImage: nil) { image, error, cacheType, url in
                if image != nil {
                    
                }
            }
        }
    }
}

//MARK: - ImageView加载Gif
extension UIImageView {
    public func startGifWithImageName(_ gifname:String){
        if gifname.lowercased().hasPrefix("file://"){
//相册路径=file:///private/var/mobile/Containers/Data/Application/xxx/tmp/BD2C7064-6403-444F-9CFA-5BEC85751195.gif
            self.startGifWithFilePath(filePath: gifname)
        }else{
            guard let path = Bundle.main.path(forResource: gifname, ofType: "") else {
                print("SwiftGif: Source for the image does not exist")
                return
            }//资源地址 = /private/var/containers/Bundle/Application/8B0747A7-92AD-4838-835D-3CCE5F1F39EE/HLDesktop.app/belowsea.gif
            self.startGifWithFilePath(filePath: path)
        }
    }
    
    public func startGifWithFilePath(filePath:String) {
        if filePath.lowercased().hasPrefix("file://"){
            if let imgUrl = URL(string: filePath){
                if let imgData = NSData(contentsOf: imgUrl){//当相册刚刚被访问过，此时是可以获取相册图片数据的
                    self.image = UIImage.sd_image(withGIFData: imgData as Data)
                    //sd_image确实可以显示Gif动画,而jpg等图片也能加载成功
                }
                else{
                    //但重启App后，访问相册地址会失败！原来重启App后也是可以的，我调试环境下不行，是因为每次调试都是Run重新安装，改变了App环境,导致相册路径变了，ca！！
                    //所以只要相册路径还存在，是可以成功读取的。
                }
            }
        }else{
//            /private/var/containers/Bundle/Application/8B0747A7-92AD-4838-835D-3CCE5F1F39EE/HLDesktop.app/belowsea.gif
            if let imgData = NSData(contentsOfFile: filePath){ //本地资源文件路径不能转成URL读取
                self.image = UIImage.sd_image(withGIFData: imgData as Data)
            }
        }
    }
    
    public func startGifWithImageData(imgdata:NSData?){
        if let data = imgdata{
//方法1:
            self.image = UIImage.sd_image(withGIFData: data as Data) //直接用SDWebImage一句搞定
            
//方法2.从data中读取数据，转换为CGImageSource
//            guard let imageSource = CGImageSourceCreateWithData(data, nil) else {
//                print("SwiftGif: imageSource for the image is nil")
//                return
//            }
//            let imageCount = CGImageSourceGetCount(imageSource)
//            if imageCount < 2{
////表示为非动画图片
//                self.image = UIImage(data: data as Data)
//                return
//            }
//            //3.遍历所有图片
//            var images = [UIImage]()
//            var totalDuration : TimeInterval = 0
//            for i in 0..<imageCount {
//                //3.1取出图片
//                guard let cgImage = CGImageSourceCreateImageAtIndex(imageSource, i, nil) else {
//                    print("SwiftGif: cgImage for the image is nil")
//                    continue
//                }
//                let image = UIImage(cgImage: cgImage)
//                images.append(image)
//
//                //3.2取出持续时间
//                guard let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, i, nil) as? NSDictionary else {
//                    print("SwiftGif: properties for the image is nil")
//                    continue
//                }
//                guard let gifDict = properties[kCGImagePropertyGIFDictionary] as? NSDictionary else {
//                    print("SwiftGif: gifDict for the image is nil")
//                    continue
//                }
//                guard let frameDuration = gifDict[kCGImagePropertyGIFDelayTime] as? NSNumber else {
//                    print("SwiftGif: frameDuration for the image is nil")
//                    continue
//                }
//                totalDuration += frameDuration.doubleValue
//            }
//
//
//            //4.设置imageview的属性
//            self.animationImages = images
//            self.animationDuration = totalDuration
//            self.animationRepeatCount = 0
//
//            //5.开始播放
//            self.startAnimating()
        }
    }
    
    public func stopGifAnimating() {
        if self.animationImages != nil{
            self.stopAnimating()
        }
    }

}


extension UIImage {
    func imageColor()->UIColor?{
        return UIColor(patternImage: self)
    }
//MARK: - 改变Image大小
    func resize(_ size: CGSize) -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
        self.draw(in: CGRect(origin: .zero, size: size))
        let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return resizedImage
    }
    
//MARK: - 拉伸或平铺Image的指定区域
    func tileOrStretchImageInsets(capInsets:UIEdgeInsets,resizingMode:UIImage.ResizingMode)->UIImage?{
//气泡拉伸，按钮背景拉伸等地方适用
        return self.resizableImage(withCapInsets: capInsets, resizingMode: resizingMode)
        //resizableImage该方法返回的是UIImage类型的对象,即返回经该方法拉伸后的图像
        //传入的第一个参数capInsets是UIEdgeInsets类型的数据,即原始图像要被保护的区域
        //UIImageResizingModeTile,     平铺
        //UIImageResizingModeStretch,  拉伸
        //参考：https://www.jianshu.com/p/e8c38c44b15b
    }
    
//MARK: - 重定向相册照片
//将相机拍摄的照片直接绘制到imageview，然后将imageview的image，转成nsdata保存到沙箱，然后下次取出来绘制到imageview上去时，发现可能会自动变成横屏的照片
//这是因为，直接接使用相机拍摄的照片不一定是UIImageOrientationUp的，需要用UIImagePNGRepresentation进行转换
//故在将image保存至相册前，或取出image绘制到ImageView前，都可以调用此方法来保证照片方向UIImageOrientationUp的
    func fixAlbumPhotoOrientation() -> UIImage?{
        if self.imageOrientation == .up{
                return self
        }else{
            UIGraphicsBeginImageContext(self.size)
            self.draw(in: CGRect.init(origin: CGPoint.init(x: 0, y: 0), size: self.size))
            let normalizedimage = UIGraphicsGetImageFromCurrentImageContext()
            return normalizedimage
        }
    }
}



extension String{
    func is_SF_Ico()->Bool{
        let lowSelf = self.lowercased()
        if lowSelf.hasPrefix("http"){
            return false //网络图标
        }else if lowSelf.hasSuffix(".png") || lowSelf.hasSuffix(".jpg") || lowSelf.hasSuffix(".jpeg") || lowSelf.hasSuffix(".webp")
                    || lowSelf.contains("@")  || lowSelf.hasPrefix("file://"){
            return false //本地图标
        }else if !self.isEmpty{
            return true //系统图新示
        }
        return false
    }
    
    
    func hl_md5()->String{
        if let messageData = self.data(using: .utf8){
            let digestData = Insecure.MD5.hash (data: messageData)
            let digestHex = String(digestData.map { String(format: "%02hhx", $0) }.joined().prefix(32))
            return digestHex
        }else{
            return self
        }
    }
    
}

extension UITextField{
    // 在文本框的左边添加一个图标
    public func showLeftIcon(_ image: UIImage?, frame: CGRect, imageSize: CGSize) {
        let leftView = UIView()
        leftView.frame = frame
        let imgView = UIImageView()
        leftView.addSubview(imgView)
        
        imgView.frame = CGRect(x: frame.width/2 - imageSize.width/2, y: frame.height/2 - imageSize.height/2, width: imageSize.width, height: imageSize.height)
        imgView.image = image
        
        self.leftView = leftView
        self.leftViewMode = .always
    }
}


extension UIViewController{
//MARK: - 判断当前是否深色模式
    func isDarkMode()->Bool{
        var isNight = false
        if #available(iOS 13.0, *) {
            isNight = view.traitCollection.userInterfaceStyle == .dark
        } else {
            isNight = UserDefaults.standard.bool(forKey: "scense.mode.isNight")
        }
        return isNight
    }
    
    func setDarkMode(_ isNight:Bool){
        UserDefaults.standard.set(isNight, forKey: "scense.mode.isNight")
        UserDefaults.standard.synchronize()
    }
    
//MARK: - 项目特殊方法·运行测试
    func testRunWithCmd(_ content:String,_ telPhoneNum:String? = nil){
    //分解出url，然后执行
        let arr = content.components(separatedBy: "&|&") //至少需传进url
        var sUrlScheme = ""
        var sTitle = ""
        var sStoreLink = ""
        for perData in arr{
            if perData.hasPrefix("url="){
                sUrlScheme = (perData as NSString).substring(from: 4) as String
            }else if perData.hasPrefix("title="){
                sTitle = (perData as NSString).substring(from: 6) as String
            }
            else if perData.hasPrefix("storelink="){
                sStoreLink = (perData as NSString).substring(from: "storelink=".count) as String
            }
        }
        sUrlScheme = sUrlScheme.trimSpace()
        if sUrlScheme.isEmpty{
            self.view.showToast("打开失败,无效的启动命令!")
            return
        }
        
        if sUrlScheme.hasPrefix("url=tel://%@"){
            if telPhoneNum != nil && !telPhoneNum!.isEmpty{
                HLUniversalFunc.openAppScheme("tel://"+telPhoneNum!, nil)
            }else{
                self.view.showToast("须先设定好要拨打的电话号码！")
                //need.update:显示三态对话框，增加帮助提示
            }
            return
        }
        
        sTitle = sTitle.trimSpace()
        sStoreLink = sStoreLink.trimSpace()
        HLUniversalFunc.openAppScheme(sUrlScheme) { success in
            if !success{
                let keyTitle = AiScreenFunc.splitTitleToGetKeyword(title: sTitle,urlScheme: sUrlScheme)
                let dlgTitle = String(format: "无法打开此应用%@", "："+sTitle)
                if sStoreLink.isEmpty{
                    //进一步判断需否去搜索,关键词不能为空
                    if keyTitle == nil || keyTitle!.isEmpty{
                        self.view.showToast("打开失败，请确保已安装此应用且启动命令无误")
                        return
                    }
                    self.alertMessage(dlgTitle, message: "请确保已安装此应用且启动命令无误", okTitle: "在 App Store 搜索", otherTitle: "帮助") {
                        let keyStr = keyTitle!.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)
                        let storeUrl = String(format: "https://itunes.apple.com/WebObjects/MZStore.woa/wa/search?mt=8&submit=edit&term=%@#software", keyStr!)
                        HLUniversalFunc.openSchemeUrl(storeUrl, nil)
                    } midBlock: {
                        HLUniversalFunc.openSchemeUrl("http://apk.hhkyj.com/", nil)
                    }
                }else{
                    //提示需否去下载安装
                    self.alertMessage(dlgTitle, message: "您尚未安装此应用或启动命令有误", okTitle: "去 App Store 下载安装", otherTitle: "帮助") {
                        HLUniversalFunc.openAppScheme(sStoreLink,nil)
                    } midBlock: {
                        HLUniversalFunc.openSchemeUrl("http://apk.hhkyj.com/", nil)
                    }
                }
                
            }
        }
        
    }
    
//MARK: - 普通弹窗
    func alertMessage(_ title: String, message: String) {
        let vc = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert)
        vc.addAction(UIAlertAction(title: "知道了", style: .cancel, handler: nil))
        present(vc, animated: true, completion: nil)
    }
    
    func alertMessage(_ title: String, message: String,iRed:Bool=false, okBlock:os_block_t?,canelBlock:os_block_t?=nil) {
        let vc = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert)
        vc.addAction(UIAlertAction(title: "确定", style:  iRed ? .destructive : .default, handler: { OKAction in
            okBlock?()
        }))
        vc.addAction(UIAlertAction(title: "取消", style: .cancel, handler: { cancelAction in
            canelBlock?()
        }))
        present(vc, animated: true, completion: nil)
    }
    
//三个按钮的系统对话框(类似：删除App/从屏幕移除/好的,3个按钮会自动排成三行按钮样式)
    func alertMessage(_ title: String, message: String,okTitle:String,otherTitle:String,cancelTitle:String? = "好的",iRed:Int?=0,okBlock:os_block_t?=nil,midBlock:os_block_t?=nil) {
        let vc = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert)
        vc.addAction(UIAlertAction(title: okTitle, style: (1 == iRed) ?.destructive : .default, handler: { OKAction in
           okBlock?()
        }))
        vc.addAction(UIAlertAction(title: otherTitle, style: (2 == iRed) ?.destructive : .default, handler: { OKAction in
            midBlock?()
        }))
        vc.addAction(UIAlertAction(title: cancelTitle, style: .cancel, handler: nil))
        present(vc, animated: true, completion: nil)
    }
//2个按钮的sheet弹窗
    func alertSheetMessage(_ title: String, message: String,selection_1:String,selection_2:String,iRed:Int?=0,okBlock:os_block_t?=nil,midBlock:os_block_t?=nil) {
        let vc = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.actionSheet)
        vc.addAction(UIAlertAction(title: selection_1, style: (1 == iRed) ?.destructive : .default, handler: { action in
           okBlock?()
        }))
        vc.addAction(UIAlertAction(title: selection_2, style: (2 == iRed) ?.destructive : .default, handler: { action in
            midBlock?()
        }))
        vc.addAction(UIAlertAction(title: "取消", style: .cancel, handler: nil)) //须有个cancel类型的按钮，才会点击空白关闭对话框
        present(vc, animated: true, completion: nil)
    }
//多个按钮的sheet弹窗
    func alertSheetMessage(_ title: String, message: String,selections:[String],iRed:Int?=0,tapBlock:@escaping (_ actionIndex:Int)->Void) {
        let vc = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.actionSheet)
        for i in 0 ..< selections.count{
            let perTitle = selections[i]
            vc.addAction(UIAlertAction(title: perTitle, style: (iRed == i) ?.destructive : .default, handler: { action in
               tapBlock(i)
            }))
        }
        vc.addAction(UIAlertAction(title: "取消", style: .cancel, handler: nil)) //须有个cancel类型的按钮，才会点击空白关闭对话框
        present(vc, animated: true, completion: nil)
    }
    
}

//数组去重
extension Array where Element : Hashable {
    var uniqueFilter: [Element] {
        return Array(Set(self))
    }
}

extension Float {
    // 小数点后如果只是0，显示整数，如果不是，显示原来的值
    var cleanZero : String {
        return self.truncatingRemainder(dividingBy: 1) == 0 ? String(format: "%.0f", self) : String(self)
    }
 
}

extension Double {
    // 小数点后如果只是0，显示整数，如果不是，显示原来的值
    var cleanZero : String {
        return self.truncatingRemainder(dividingBy: 1) == 0 ? String(format: "%.0f", self) : String(self)
    }
}

extension UIButton {
//按钮下划线字体
    func showUnderlineStyle() {
        guard let text = self.titleLabel?.text else { return }
        let attributedString = NSMutableAttributedString(string: text)
        //NSAttributedStringKey.foregroundColor : UIColor.blue
        attributedString.addAttribute(NSAttributedString.Key.underlineColor, value: self.titleColor(for: .normal)!, range: NSRange(location: 0, length: text.count))
        attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: self.titleColor(for: .normal)!, range: NSRange(location: 0, length: text.count))
        attributedString.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: NSRange(location: 0, length: text.count))
        self.setAttributedTitle(attributedString, for: .normal)
    }
}



